package com.xxw.springbootencrypt.util;

import com.xxw.springbootencrypt.config.RsaConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
*@ClassName: RSAUtil
*@Description: RSA公钥/私钥/加密解密
*@Author xxw
*@Date 2021/3/28
*/

@Component
public class RsaUtil {

    // 加密算法 RSA
    public static final String KEY_ALGORITHM = "RSA";

    // RSA最大加密明文大小
    private static final int MAX_ENCRYPT_BLOCK = 117;

    // RSA最大解密密文大小
	private static final int MAX_DECRYPT_BLOCK = 128;

    // RSA 位数
    private static final int INITIALIZE_LENGTH = 1024;

    @Autowired
    private RsaConfig rsaConfig;

    /**
    *@ClassName: RSAUtils
    *@Description: 生成秘钥对： 公钥私钥
    *@Params: 
    *@Return: 
    *@Author xxw
    *@Date 2021/2/14
    */

    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        SecureRandom secureRandom = new SecureRandom(secret.getBytes()); // 用盐值生成加密基本的随机数
        keyPairGenerator.initialize(INITIALIZE_LENGTH,secureRandom); // RSA 位数 + 随机数 初始化一个 KeyPairGenerator对象
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        // 生成公钥并存入文件
        String publicKey = Base64Util.encode(keyPair.getPublic().getEncoded());
        FileUtil.writeFile(publicKeyFilename, publicKey.getBytes());

        // 生成私钥并存入文件
        String privateKey = Base64Util.encode(keyPair.getPrivate().getEncoded());
        FileUtil.writeFile(privateKeyFilename,privateKey.getBytes());
    }



    /**
    *@ClassName: RSAUtil
    *@Description: 从 Base64Util.encode(byte[](publicKey.getEncoded())) 还原回公钥，适用于RSA算法
    *@Params:
    *@Return:
    *@Author xxw
    *@Date 2021/3/28
    */
    public static PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // X509EncodedKeySpec 返回按照 X.509 标准进行编码的密钥的字节。
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(publicKey));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     *@ClassName: RSAUtil
     *@Description: 从 Base64Util.encode(byte[](publicKey.getEncoded())) 将还原回私钥，适用于RSA算法
     *@Params:
     *@Return:
     *@Author xxw
     *@Date 2021/3/28
     */
    public static PrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // PKCS8EncodedKeySpec 返回按照 PKCS8 标准进行编码的密钥的字节。
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }


    /**
    *@ClassName: RSAUtil
    *@Description: 公钥加密
    *@Params:
    *@Return:
    *@Author xxw
    *@Date 2021/3/28
    */
    public byte[] encryptData(byte[] data){

        try {
            // 获取 Base64 加密的公钥
            String publicKey = FileUtil.readFile(rsaConfig.getPubKeyFileUrl());

            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            // 编码前设定编码方式及密钥
            cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));

            // 分段加密, 输出是 Bsae64 再加密一次，避免数据是二进制乱码
            return Base64Util.encode(segmentDataPretreatment(MAX_ENCRYPT_BLOCK,data,cipher)).getBytes();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *@ClassName: RSAUtil
     *@Description: 私钥解密
     *@Params:
     *@Return:
     *@Author xxw
     *@Date 2021/3/28
     */
    public byte[] decryptData(byte[] encryptedData){

        // 先对数据进行Base64解密
        encryptedData = Base64Util.decode(new String(encryptedData));

        try {

            String privateKey = FileUtil.readFile(rsaConfig.getPriKeyFileUrl());

            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);

            cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));

            // 分段解密
            return segmentDataPretreatment(MAX_DECRYPT_BLOCK,encryptedData,cipher);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
    *@ClassName: RsaUtil
    *@Description: 分段数据加密解密预处理
    *@Params: maxLength： 最大密/明文长度， data： 要分段的数据， ciper
    *@Return:
    *@Author xxw
    *@Date 2021/3/28
    */
    public static byte[] segmentDataPretreatment(int maxLength, byte[] data, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
        // 存储分段 dofinal 的加密后的数据
        ArrayList<byte[]> list = new ArrayList<>();
        // 传入数据并返回解密结果, 采用分段解密
        for (int i = 0; i < data.length; i += maxLength) {

            byte doFinal[];
            byte tempArr[] = null;
            // 判断是否超出 解密/加密的最大长度
            if (data.length - i > maxLength) {
                tempArr = new byte[maxLength];
                System.arraycopy(data, i, tempArr, 0, maxLength);
                doFinal = cipher.doFinal(tempArr);
            } else {
                tempArr = new byte[data.length - i];
                System.arraycopy(data, i, tempArr, 0, data.length - i);
                doFinal = cipher.doFinal(tempArr);
            }
            // 累加
            list.add(doFinal);
        }
        // doFinal 总长度
        int fLengthSum = list.stream().mapToInt(item -> item.length).sum();
        System.out.println(fLengthSum);

        // 定义处理后的数据
        byte[] atfTreatData = new byte[fLengthSum];

        // 目标数据中的起始位置
        AtomicInteger desPos = new AtomicInteger();
        // 加密结果合并
        list.forEach(item -> {
            // 累加
            System.arraycopy(item, 0, atfTreatData, desPos.get(), item.length);
            // 获取旧值并添加新值
            desPos.addAndGet(item.length);
        });

        return atfTreatData;
    }
}

